package performance;

import java.util.ArrayList;
import java.util.List;

import persi.casosDeUso.CasoDeUso;
import persi.hibernate.HibernateUtils;

public class Profiler {
	private static final String ROUNDS = "rondas";
	private static final String ITERATION = "iteraciones";
	private static Properties properties = Properties
			.read("./src/main/resources/profiler.properties");
	final static private List<Profiler> profilers = new ArrayList<Profiler>();

	private static int getRounds() {
		return Profiler.rounds;
	}

	private static void setRounds(final int rounds) {
		Profiler.rounds = rounds;
	}

	private final Class<? extends Runnable> casoDeUso;

	private OutputProfilerPrinter output;
	private long minTime = Long.MAX_VALUE;
	private long maxTime = Long.MIN_VALUE;
	private long incrementalTime = 0;

	private static int rounds = 1;
	private static int iterations = 1;

	/**
	 * Initicializo hibernate para evitar los tiempos de inicializacion
	 */

	static {
		HibernateUtils.createSession();
		HibernateUtils.beginTransaction();
		HibernateUtils.closeSession();
	}

	private static int getIterations() {
		return Profiler.iterations;
	}

	public static void profile(final String name,
			final Class<? extends Runnable> casoDeUso) {
		final Profiler profiler = new Profiler(name, casoDeUso);
		Profiler.profilers.add(profiler);
		profiler.profile();
	}

	private static void setIterations(final int iterations) {
		Profiler.iterations = iterations;
	}

	public Profiler(final String name, final Class<? extends Runnable> casoDeUso) {
		this.casoDeUso = casoDeUso;
		this.initialize(name);
	}

	private void createFinalReport() {
		final int total = Profiler.getRounds() * Profiler.getIterations();
		this.getOutput()
				.print("El tiempo total fue de ")
				.print(this.incrementalTime)
				.printLn(" milisegundos")
				//
				.print("El mayor tiempo fue de ")
				.print(this.maxTime)
				.printLn(" milisegundos")
				//
				.print("El menor tiempo fue de ").print(this.minTime)
				.printLn(" milisegundos")
				//
				.print("El tiempo promedio fue de ")
				.print(this.incrementalTime / total).printLn(" milisegundos");

	}

	private Class<? extends Runnable> getCasoDeUso() {
		return casoDeUso;
	}

	private OutputProfilerPrinter getOutput() {
		return output;
	}

	private void initialeValues() {
		Profiler.setRounds(Integer.valueOf(Profiler.properties
				.get(Profiler.ROUNDS)));
		Profiler.setIterations(Integer.valueOf(Profiler.properties
				.get(Profiler.ITERATION)));
	}

	private void initialize(final String name) {
		initializeOutput(name);
		this.initialeValues();
	}

	private void initializeOutput(final String name) {
		this.setOutput(new OutputProfilerPrinter(Profiler.properties, name));
	}

	public void profile() {
		long time = 0;
		for (int round = 0; round < Profiler.getRounds(); round++) {
			for (int iteration = 0; iteration < Profiler.getIterations(); iteration++) {
				final long initIterationTime = System.currentTimeMillis();
				new CasoDeUso() {

					@Override
					public void casoDeUso() {
						try {
							getCasoDeUso().newInstance().run();
						} catch (final Exception e) {
							throw new ProfilerException(e);
						}

					}
				}.ejecutar();
				final long finishIterationTime = System.currentTimeMillis();
				time = finishIterationTime - initIterationTime;
				this.singleUseCaseReport(time);
				this.updateStatistics(time);
			}
		}
		this.createFinalReport();
	}

	private void setOutput(final OutputProfilerPrinter output) {
		this.output = output;
	}

	private OutputProfilerPrinter singleUseCaseReport(final long time) {
		return this.getOutput().print("tardo en ejecutarse ").print(time)
				.printLn(" milisegundos.");
	}

	private void updateStatistics(final long time) {
		this.incrementalTime = this.incrementalTime + time;
		if (this.maxTime < time) {
			this.maxTime = time;
		}
		if (minTime > time) {
			this.minTime = time;
		}

	}

}
